package com.lyf.boot.web.advice;

import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.SecureUtil;
import com.lyf.boot.annotation.Crypto;
import com.lyf.boot.properties.CryptoProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * @author Luyufan
 * @date 2023/8/15 15:57
 */
@Slf4j
@RequiredArgsConstructor
@ControllerAdvice
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {

    private final CryptoProperties cryptoProperties;

    @Override
    public boolean supports(@NonNull MethodParameter methodParameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    @NonNull
    public HttpInputMessage beforeBodyRead(@NonNull HttpInputMessage inputMessage, @NonNull MethodParameter methodParameter, @NonNull Type type, Class<? extends HttpMessageConverter<?>> clazz) throws IOException {
        try {
            boolean inEncode = false;
            boolean inPrint = false;
            //先获取类上有没有注解
            if(Objects.requireNonNull(methodParameter.getMethod()).getDeclaringClass().isAnnotationPresent(Crypto.class)){
                //获取注解配置的包含和去除字段
                Crypto crypto = methodParameter.getMethod().getDeclaringClass().getAnnotation(Crypto.class);
                //入参是否需要解密
                inEncode = crypto.inDecode();
            }
            //在获取方法上有没有注解
            if(methodParameter.getMethod().isAnnotationPresent(Crypto.class)){
                //获取注解配置的包含和去除字段
                Crypto crypto = methodParameter.getMethodAnnotation(Crypto.class);
                //入参是否需要解密
                assert crypto != null;
                inEncode = crypto.inDecode();
            }
            if(inEncode &&  cryptoProperties.isEnabled()){
                log.info("对方法method :【{}】返回数据进行解密", methodParameter.getMethod().getName());
                return new CryptoHttpInputMessage(inputMessage);
            }
        } catch (Exception e) {
            log.error("对方法method :【{}】返回数据进行解密出现异常：{}", Objects.requireNonNull(methodParameter.getMethod()).getName(), e.getMessage());
        }
        return inputMessage;
    }

    @Override
    @NonNull
    public Object afterBodyRead(@NonNull Object body, @NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    @NonNull
    public Object handleEmptyBody(Object body, @NonNull HttpInputMessage inputMessage, @NonNull MethodParameter parameter, @NonNull Type targetType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    class CryptoHttpInputMessage implements HttpInputMessage {
        private final HttpHeaders httpHeaders;

        private final InputStream body;

        public CryptoHttpInputMessage(HttpInputMessage inputMessage) throws Exception{
            this.httpHeaders = inputMessage.getHeaders();
            String bodyStr = IoUtil.read(inputMessage.getBody(), StandardCharsets.UTF_8);
            this.body = IoUtil.toStream(SecureUtil.aes(cryptoProperties.getAseKey().getBytes()).decryptStr(bodyStr), StandardCharsets.UTF_8);
        }

        @Override
        @NonNull
        public InputStream getBody() {
            return body;
        }

        @Override
        @NonNull
        public HttpHeaders getHeaders() {
            return httpHeaders;
        }
    }
}
